/*
board-games:	An educational framework for board games programming.
    Copyright (C) 2008  Daniel Gutson, FuDePAN
    
    This file is part of board-games.

    board-games is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    board-games is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
*/

#include <iostream>

#include "referee_sdk/referee.h"
#include "common_sdk/board.h"
#include "referee_sdk/board_painter.h"
#include "connect4_proxy.h"

struct Move
{
    Coord row;
    Coord col;
};

class Connect4Referee : public Referee
{
    Board<PLAYERS, BOARD_ROWS, BOARD_COLS> board;
    
    size_t empty_places;
    
    class Connect4Painter : public BoardPainter<PLAYERS, BOARD_ROWS, BOARD_COLS>
    {	
    	virtual std::string paint_cell(const PLAYERS& cell_value, size_t cell_row) const
    	{
    	    if (cell_value == BOARD_EMPTY)
    	    {
        		return "    ";
    	    }
    	    else
    	    {
        		if ((cell_row == 0 && cell_value == BOARD_X) || (cell_row == 1 && cell_value == BOARD_O))
        		    return " \\/ ";
        		else
        		    return " /\\ ";
    	    }
    	}
	
    public:
    	Connect4Painter()
    	    : BoardPainter<PLAYERS, BOARD_ROWS, BOARD_COLS>(std::cout)
    	{
    	    upper_left = upper_mid_sep = upper_right = cross_sep = lower_left = lower_mid_sep = lower_right = "+";
    	    left_mid_sep = right_mid_sep = inner_vert_delim = outer_vert_delim = "|";
    	    inner_horiz_delim = outer_horiz_delim = "----";
    	    cell_height = 2;
    	}
    } const painter;
    
    PlayersProxy* const playersProxy;
    
    virtual bool accepts_number_of_players(size_t players)
    {
        return players == 2;
    }
    
    void tell_player_its_team(size_t player, PLAYERS team)
    {
        playersProxy->send_message(player, team);
    }
    
    static PLAYERS revert(PLAYERS p)
    {
        return static_cast<PLAYERS>(3-p);
    }
    
    void show_board() const
    {
    	painter.paint(board);
    }
    
    bool valid_move(Coord col, Move& move) const
    {
        if (col>=0 && col<BOARD_COLS)
        {
            // calculate row:
            bool valid;
            move.row = BOARD_ROWS;
            move.col = col;
            do
            {
                move.row--;
                valid = (board[move.row][move.col] == BOARD_EMPTY);
            }
            while(!valid && move.row>0);
            
            return valid;
        }
        else
            return false;
    }
    static bool in_range(signed int row, signed int col)
    {
        return 
            row >= 0 && row < BOARD_ROWS &&
            col >= 0 && col < BOARD_COLS;
    }
#if 0
    
    static bool advance(const Move& m0, unsigned int direction, signed int step, Move& m1)
    {
        static const signed int 
            dx[8] = {  1,  1,  0, -1, -1, -1,  0,  1 },
            dy[8] = {  0, -1, -1, -1,  0,  1,  1,  1 };
            
        const bool ret = ( 
            in_range(static_cast<signed int>(m0.row) + step * dy[direction]) &&
            in_range(static_cast<signed int>(m0.col) + step * dx[direction]));
            
        if (ret)
        {
            m1.row = m0.row + step * dy[direction];
            m1.col = m0.col + step * dx[direction];
        }
        
        return ret;    
    }
#endif

    size_t count_equal(const Move& move, signed int dx, signed int dy) const
    {
        const PLAYERS team = board[move.row][move.col];
        signed int row(move.row), col(move.col);
        size_t count(0);
        bool valid_pos;
        bool equal = false;
        
        do
        {
            count++;
            row += dy;
            col += dx;
            valid_pos = in_range(row,col);
            if (valid_pos)
                equal = (board[row][col] == team);
        }
        while(valid_pos && equal);
        
        return count-1;
    }
    
    bool is_winning_move(const Move& move) const
    {
        bool winning;
        static const signed int 
            dx[4] = {  1,  1,  0, -1  },
            dy[4] = {  0, -1, -1, -1  };
            
        size_t count_positive = 0;
        size_t count_negative = 0;
        size_t direction = 0;
        
        do
        {
            count_positive = count_equal(move, dx[direction], dy[direction]);
            count_negative = count_equal(move, -dx[direction], -dy[direction]);
            winning = (count_positive + 1 + count_negative) >= 4;
            direction++;
        }
        while (!winning && direction<4);

        return winning;
    }
    
    
    virtual Winner do_match() throw(PlayerID, const char*) 
    {
        Winner winner = BOARD_EMPTY;
        
        Move prev_move;
        Coord played_col;
        PLAYERS p = BOARD_X;
        bool game_continues = true;

        tell_player_its_team(0, BOARD_X);
        tell_player_its_team(1, BOARD_O);
        playersProxy->send_board(ALL_PLAYERS, board);
        
        
        while (empty_places>0 && 
                playersProxy->player_running(ALL_PLAYERS) &&
                game_continues )
        {
            playersProxy->receive_message(p-1, played_col);
            if (valid_move(played_col, prev_move))
            {
                board[prev_move.row][prev_move.col] = p;
                empty_places--;
                
                if (is_winning_move(prev_move))
                {
                    winner = p;
                    game_continues = false;
                }
                else
                {
                    //tell the other player to keep playing
                    p = revert(p);
                    playersProxy->send_message(p-1, true);
                }
            }
            else
            {
                game_continues = false;
                std::cout << "INVALID! " << prev_move.row << " " << prev_move.col << std::endl;
                winner = revert(p);
            }
            
            show_board();

        }

        // shut down players:
        playersProxy->send_message(0, false);
        playersProxy->send_message(1, false);

        return winner;
    }
    
public:
    Connect4Referee(PlayersProxy* playersProxy)
        : board(BOARD_EMPTY), playersProxy(playersProxy)
    {                        
        empty_places = board.number_of_cells();
    }
};

Referee* initialize(PlayersProxy* playersProxy)
{
    return new Connect4Referee(playersProxy);
}

